package com.mysen.sso.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import com.mysen.exception.MaikeServiceException;
import com.mysen.init.Initatial;
import com.mysen.json.JSON;
import com.mysen.json.JSONArray;
import com.mysen.json.JSONEnv;
import com.mysen.json.JSONFunc;
import com.mysen.json.JSONObject;
import com.mysen.json.UnmodifiableJSONArray;
import com.mysen.util.IOUtils;
import com.mysen.util.MapUtil;
import com.mysen.util.ReflectUtil;

public class ValidationConfig implements Initatial {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(ValidationConfig.class);
	
	public static final String VALIDATION_CONFIG_PATH = "/validation"; 
	public static final String KEY_REQUIRED = "required"; 
	public static final String KEY_VALIDATION = "validation"; 
	public static final String KEY_ERRORMSG = "errorMsg"; 
	
	public static final String KEY__CHECKFORANNOTATION = "_checkForAnnotation"; 
	public static final String KEY__USERCHECK = "_userCheck"; 
	public static final String KEY__BRIDGEBEANNAME = "_bridgeBeanName";
	
	public static final JSONArray DEFAULT_CHECKFOR_ANNOTATION = 
			new UnmodifiableJSONArray(
			"org.springframework.web.bind.annotation.RequestParam",
			"org.springframework.web.bind.annotation.RequestBody",
			"org.springframework.web.bind.annotation.PathVariable"
			).lock();
	
	public static final JSONFunc DEFAULT_USER_CHECK = new JSONFunc("checkUserAny");
	
	private static final JSONObject validations = new JSONObject();
	
	private ValidationConfig(){}
	
	private static ValidationConfig instance = new ValidationConfig();
	
	public static ValidationConfig getInstance() {
		return instance;
	}

	public void init() {
		FileInputStream stream = null;
		URL url = ValidationConfig.class.getResource(VALIDATION_CONFIG_PATH);
		if(url == null){
			LOGGER.warn("validation config path not found!");
			return;
		}
		String path = url.getFile();
		File file = new File(path);
		if(file.exists() && file.isDirectory()){
			String[] fs = file.list();
			for(String f : fs){
				File configFile = new File((path.endsWith(File.separator) ? path : path + File.separator) + f);
				if(configFile.exists() && configFile.isFile() && f.endsWith(".json")){
					try {
						LOGGER.info("read validation config file:{}", configFile);
						stream = new FileInputStream(configFile);
						Object contextObj = JSON.parse(stream);
						if(contextObj instanceof JSONObject){
							validations.putAll((JSONObject)contextObj);
						}else if(contextObj instanceof JSONArray){
							for(int i = 0; i < ((JSONArray) contextObj).size() ; i++) {
								validations.putAll(((JSONArray) contextObj).getJSONObject(i));
							}
						}else{
							throw new InstantiationException("configFile : "+configFile+" content is not json data!");
						}
					} catch (FileNotFoundException | InstantiationException e) {
						throw new MaikeServiceException("init validation error!", e);
					} finally {
						IOUtils.close(stream);
					}
				}
			}
		}else{
			LOGGER.warn("validation config path can not be a file!!!");
		}
	}

	public static<T> T getValidationConfig(String uri,String type){
		JSONObject object = validations.getJSONObject(uri);
		if(object == null){
			return null;
		}
		return (T) object.get(type);
	}

	@Override
	public void init(ApplicationContext ctx) throws MaikeServiceException {
		
	}
	
	public static boolean validate(JSONObject config, Object parameters, List<Object> errors, Object bridge) {
		Boolean res = Boolean.TRUE;
		if(MapUtil.isEmpty(config))
			return res;
		
		Iterator<Entry<String, Object>> iterator = config.entrySet().iterator();
		while(iterator.hasNext()) {
			Entry<String, Object> entry = iterator.next();
			String key = entry.getKey();
			Object options = entry.getValue();
			if(!(options instanceof JSONObject)) {
				throw new MaikeServiceException("options must be JSONObject!!!");
			}
			Object val = null;
			
			if(parameters instanceof Map<?,?>){
				Map<?, ?> param = (Map<?, ?>) parameters;
				if(param.containsKey(key)){
					val = param.get(key);
				}else if(param.containsKey(null)){
					Object obj = param.get(null);
					if(obj instanceof Map){
						val = ((Map<?,?>) obj).get(key);
					}else{
						val = ReflectUtil.getVal(obj, key);
					}
				}
				if(val instanceof List<?>){
					List<?> list = (List<?>) val;
					for(int i = 0;i < list.size(); i++){
						if(!fillErrors((JSONObject)options, i, list.get(i), errors, bridge)){
							res = false;
						}
					}
				}else if(!fillErrors((JSONObject)options, key, val, errors, bridge)){
					res = false;
				}
			}else if(parameters instanceof List<?>){
				List<?> list = (List<?>) parameters;
				for(int i = 0;i < list.size(); i++){
					if(!fillErrors((JSONObject)options, i, list.get(i), errors, bridge)){
						res = false;
					}
				}
			}else{
				val = ReflectUtil.getVal(parameters, key);
				if(!fillErrors((JSONObject)options, key, val, errors, bridge)){
					res = false;
				}
			}
		}
		return res;
	}

	private static Boolean fillErrors(JSONObject options, Object key, Object val, List<Object> errors, Object bridge) {
		Boolean required = options.getBoolean(KEY_REQUIRED, false);
		Object validation = options.get(KEY_VALIDATION);
		String errorMsg = options.getString(KEY_ERRORMSG, "validate not pass");
		
		Boolean res = Boolean.TRUE;
		if(val == null) {
			if(required) {
				LOGGER.info("valid failt required val is null!");
				errors.add(MapUtil.newHashMap(key,errorMsg));
				res = false;
			}
		} else {
			if(validation != null) {
				boolean tempres = true;
				if(validation instanceof JSONArray) {
					boolean find = false;
					for(Object obj : (JSONArray)validation){
						if(obj instanceof Number){
							double value = ((Number) obj).doubleValue();
							double value2 = ((Number)val).doubleValue();
							if(value != value2){
								find = true;
								break;
							}
						} else if(obj.equals(val)){
							find = true;
							break;
						}
					}
					if(!find) {
						errors.add(MapUtil.newHashMap(key,errorMsg));
						tempres = false;
					}
				} else if(validation instanceof String) {
					if(!Pattern.compile((String)validation).matcher(String.valueOf(val)).find()) {
						errors.add(MapUtil.newHashMap(key,errorMsg));
						tempres = false;
					}
				} else if(validation instanceof JSONFunc) {
					Map<Object, Object> map = new HashMap<Object, Object>() ;
					map.put(key, val);
			        map.put("this", options);
			        map.put(null, val);
					Object obj = ((JSONFunc) validation).exec(new JSONEnv(map, bridge));
					if(obj == null || !obj.toString().equals("true")){
						errors.add(MapUtil.newHashMap(key,errorMsg));
						tempres = false;
					}
				} else if(validation instanceof JSONObject) {
					if(!validate((JSONObject)validation, val, errors, bridge)){
						tempres = false;
					}
				} else 
					throw new MaikeServiceException("not support validation type : " + validation.getClass()); 
				res = !tempres ? false : true;
				LOGGER.info("valid {} validation:{}, val:{} ", tempres?"success":"failt",validation, val);
			}
		}
		return res;
	}

}
